package com.yang.spring.core.bean.definition.builder;

import com.yang.spring.core.annotations.Autowired;
import com.yang.spring.core.annotations.Resource;
import com.yang.spring.core.bean.definition.BeanDefinition;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class AbstractBeanDefinitionBuilder implements IBeanDefinitionBuilder {
    @Override
    public List<BeanDefinition> buildBeanDefinition(Class clazz) {
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setBeanClassType(clazz);
        beanDefinition.setBeanName(
                firstCharLowerCase(clazz.getName().substring(clazz.getName().lastIndexOf(".") + 1))
        );
        richBeanDefinitionByAnnotation(beanDefinition, clazz);

        Map<String, BeanDefinition> declaredFieldName2BeanDefinitionMap = new HashMap<>();
        declaredFieldName2BeanDefinitionMap.putAll(getDependencyBeanName2BeanDefinitionMapByField(beanDefinition));
        declaredFieldName2BeanDefinitionMap.putAll(getDependencyBeanName2BeanDefinitionMapByMethod(beanDefinition));

        declaredFieldName2BeanDefinitionMap.forEach((dependencyBeanName, dependencyBeanDefinition) -> {
            beanDefinition.putDependencyBeanDefinition(dependencyBeanName, dependencyBeanDefinition);
        });

        List<BeanDefinition> beanDefinitionList = new ArrayList<>();
        beanDefinitionList.add(beanDefinition);
        return beanDefinitionList;
    }

    protected abstract void richBeanDefinitionByAnnotation(BeanDefinition beanDefinition, Class clazz);

    protected Map<String, ? extends BeanDefinition> getDependencyBeanName2BeanDefinitionMapByMethod(BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getBeanClassType();
        Map<String, BeanDefinition> declaredFieldName2BeanDefinitionMap = new HashMap<>();

        Field[] fields = clazz.getDeclaredFields();
        Map<String, Field> declaredFieldName2FieldMap = new HashMap<>();
        for (Field field : fields) {
            declaredFieldName2FieldMap.put(field.getName(), field);
        }
        // 判断method中是否有需要注入的
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            BeanDefinition dependencyBeanDefinition = getDependencyBeanDefinitionByMethod(declaredMethod);
            if (dependencyBeanDefinition == null) {
                continue;
            }
            String fieldName = firstCharLowerCase(declaredMethod.getName().substring("set".length()));
            if (declaredFieldName2FieldMap.containsKey(fieldName)) {
                declaredFieldName2BeanDefinitionMap.put(fieldName, dependencyBeanDefinition);
            }
            richDependencyBeanDefinitionByAnnotation(dependencyBeanDefinition, beanDefinition, declaredMethod);
        }
        return declaredFieldName2BeanDefinitionMap;
    }

    protected BeanDefinition getDependencyBeanDefinitionByMethod(Method method) {
        if (!method.getName().startsWith("set")) {
            return null;
        }
        if (method.getParameterTypes().length != 1) {
            return null;
        }
        String dependencyBeanName = firstCharLowerCase(method.getName().substring("set".length()));
        Class<?> dependencyBeanType = method.getParameterTypes()[0];
        BeanDefinition dependenecyBeanDefinition = new BeanDefinition();
        dependenecyBeanDefinition.setBeanClassType(dependencyBeanType);
        dependenecyBeanDefinition.setBeanName(dependencyBeanName);
        return dependenecyBeanDefinition;
    }

    protected Map<String, ? extends BeanDefinition> getDependencyBeanName2BeanDefinitionMapByField(BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getBeanClassType();
        Map<String, BeanDefinition> declaredFieldName2BeanDefinitionMap = new HashMap<>();
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            BeanDefinition dependencyBeanDefinition = getDependencyBeanDefinitionByField(field);
            if (dependencyBeanDefinition == null) {
                continue;
            }
            richDependencyBeanDefinitionByAnnotation(dependencyBeanDefinition, beanDefinition, field);
            declaredFieldName2BeanDefinitionMap.put(field.getName(), dependencyBeanDefinition);
        }
        return declaredFieldName2BeanDefinitionMap;
    }

    protected abstract void richDependencyBeanDefinitionByAnnotation(BeanDefinition dependencyBeanDefinition,
                                                                     BeanDefinition beanDefinition, Method method);

    protected abstract void richDependencyBeanDefinitionByAnnotation(BeanDefinition dependencyBeanDefinition,
                                                                     BeanDefinition beanDefinition, Field field);

    protected BeanDefinition getDependencyBeanDefinitionByField(Field field) {
        if (!field.isAnnotationPresent(Autowired.class)
                && !field.isAnnotationPresent(Resource.class)) {
            return null;
        }
        BeanDefinition dependencyBeanDefinition = new BeanDefinition();
        dependencyBeanDefinition.setBeanClassType(field.getType());
        dependencyBeanDefinition.setBeanName(field.getName());
        return dependencyBeanDefinition;
    }

    /**
     * 首字母小写
     * @param str
     * @return
     */
    protected String firstCharLowerCase(String str) {
        if (str.length() == 1) {
            return str.toLowerCase();
        }
        return str.substring(0, 1).toLowerCase() + str.substring(1);
    }
}
